package de.sendorian.app.forumArchive.domain;

import static org.apache.commons.lang.StringUtils.containsIgnoreCase;

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.Transient;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import de.sendorian.app.forumArchive.ApplicationContextHolder;
import de.sendorian.app.forumArchive.Config;
import de.sendorian.app.forumArchive.repository.FileRepository;
import de.sendorian.util.io.FileReader;
import de.sendorian.util.io.FileWriter;


@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class File {

    private static Logger logger = Logger.getLogger(File.class.getName());

    private static final String AUTHOR_NAMES = "((?:[_-]([a-zA-Z0-9]{2,5}))+)";

    private static final String AUTHOR_NAMES_WRONG_PATTERN = "((?:-([a-zA-Z0-9]{2,5}))+)";

    private static final String NUMBER = "-(\\w)";

    private static final String FILE_EXT = "\\W\\w{3}\\z";

    // e.g. "crate_snd-a.jpg" or "crate_snd.jpg"
    private static final Pattern advancedPattern = Pattern.compile(AUTHOR_NAMES + "(" + NUMBER + ")?" + FILE_EXT);

    // matches e.g. "-a.jpg"
    private static final Pattern numberPattern = Pattern.compile(NUMBER + FILE_EXT);

    // e.g. "crate-snd-a.jpg" or "crate-snd.jpg"
    private static final Pattern wrongPattern = Pattern.compile(AUTHOR_NAMES_WRONG_PATTERN + "(" + NUMBER + ")?"
            + FILE_EXT);
    
    // e.g. "file://..." or "http://..."
    private static final Pattern uriPattern = Pattern.compile(".*://.*");

    @Id
    @GeneratedValue
    protected int id;

    @Column(length = 255)
    protected String name;

    @Column(length = 10000)
    protected String url;

    @Column(length = 32000)
    protected String localPath;

    @Transient
    private byte[] data;

    @Transient
    Config config;
    
    @Transient
    FileRepository fileRepository;


    public File() {
    }


    public int getId() {
        return id;
    }


    public String getName() {
        if (name == null && url != null) {
            name = de.sendorian.util.StringUtils.getNameFromFileURL(url);
        }
        return name;
    }


    public void setName(String name) {
        this.name = name;
    }


    public String getUrl() {
        return url;
    }


    public void setUrl(String url) {
        if (!uriPattern.matcher(url).matches()) {
            url = getConfig().getBaseUrl() + url;
        }
        this.url = StringEscapeUtils.unescapeHtml(url);
        name = null;
    }
    
    /**
     * Fluent version of setUrl()
     */
    public File withUrl(String url) {
        setUrl(url);
        return this;
    }


    public String getLocalPath() {
        return localPath;
    }


    public void setLocalPath(String lokalPath) {
        localPath = lokalPath;
    }


    /**
     * Returns the loacal path of this file (basically localPath+/+name).
     */
    public String getLocalAbsoluteFilename() {
        if (getLocalPath() == null) {
            return null;
        }

        return getLocalPath() + java.io.File.separatorChar + getName();

    }
    
    
    /**
     * Returns true if this file alredy exists on the local file system.
     */
    public boolean existsLocally() {
        if (getLocalPath() == null) {
            return false;
        } 
        
        return new java.io.File(getLocalAbsoluteFilename()).exists();
    }
    

    /**
     * Deletes the downloaded file on th elocal file system, if existing.
     */
    public boolean deleteLocalVersion() {
        if (!existsLocally()) {
            return false;
        } 
        
        return new java.io.File(getLocalAbsoluteFilename()).delete();
    }
    

    public byte[] getData() {
        if (data == null) {
            try {
                data = new FileReader().getFile(getUrl());
            } catch (IOException e) {
                logger.error("Cannot load file data from '" + url + "'", e);
            }
        }
        return data;
    }


    public void setData(byte[] data) {
        this.data = data;
    }


    @Transient
    public boolean isExternal() {
        return !StringUtils.startsWithIgnoreCase(getUrl(), getConfig().getBaseUrl());
    }


    public boolean save() throws IOException {
        if (getLocalPath() == null)
            throw new IllegalStateException("Local Path has to be defined to save the file!");

        if (data != null)
            return new FileWriter().write(data, getLocalPath(), getName());
        else
            return new FileReader().save(getUrl(), getLocalPath(), getName());
    }


    public boolean delete() throws IOException {
        if (getLocalPath() == null)
            throw new IllegalStateException("Local Path has to be defined to delete the file!");

        java.io.File file = new java.io.File(getLocalPath(), getName());
        if (file.exists())
            return file.delete();
        return false;
    }


    @Transient
    public String getAuthorAppendix() {
        if (getName() == null)
            return null;

        Matcher advancedMatcher = advancedPattern.matcher(getName());
        if (advancedMatcher.find()) {
            if (advancedMatcher.groupCount() > 1)
                return advancedMatcher.group(2);
        }
        return null;
    }


    @Transient
    public String getIndex() {
        if (getName() == null)
            return null;

        Matcher advancedMatcher = numberPattern.matcher(getName());
        if (advancedMatcher.find()) {
            if (advancedMatcher.groupCount() == 1)
                return advancedMatcher.group(1);
        }
        return null;
    }


    public String appendUserShortname(String userShortname) {
        if (userShortname != null && getName() != null) {
            String authorAppendix = getAuthorAppendix();
            if (!containsIgnoreCase(authorAppendix, userShortname)) {
                String index = getIndex();
                if (index == null) {
                    setName(getNameWithoutExtension() + "_" + userShortname + "." + getExtension());
                } else {
                    String name = getNameWithoutExtension();
                    setName(name.substring(0, name.lastIndexOf("-")) + "_" + userShortname + "-" + index + "."
                            + getExtension());
                }
            }
        }
        return getName();
    }


    public String checkAndCorrectAuthorAppendix() {
        String name = getName();
        if (name == null)
            return null;

        Matcher simpleMatcher = wrongPattern.matcher(name);
        if (simpleMatcher.find()) {
            if (simpleMatcher.groupCount() > 1) {
                String authorName = simpleMatcher.group(2);
                name = name.replace("-" + authorName, "_" + authorName);
                setName(name);
                return name;
            }
        }
        return name;
    }


    @Transient
    public String getNameWithoutExtension() {
        String name = getName();
        if (name != null) {
            int extIndex = name.indexOf('.');
            if (extIndex != -1) {
                name = name.substring(0, extIndex);
            }
        }
        return name;
    }


    @Transient
    public String getExtension() {
        String name = getName();
        if (name != null) {
            int extIndex = name.indexOf('.');
            if (extIndex != -1) {
                name = name.substring(extIndex + 1, name.length());
            }
        }
        return name;
    }
    
    
    /**
     * Stores this Entity to the database or updates it if already present.
     */
    public void store() {
        getFileRepository().saveOrUpdate(this);
    }


    private Config getConfig() {
        if (this.config == null) {
            this.config = (Config) ApplicationContextHolder.getBean("config");
        }
        return this.config;
    }
    
    
    private FileRepository getFileRepository() {
        if (this.fileRepository == null) {
            this.fileRepository = ApplicationContextHolder.getBean(FileRepository.class);
        }
        return this.fileRepository;
    }


    @Override
    public String toString() {
        return getName();
    }
}
